/*
 * Copyright (c) 2012 Universidad Antonio de Nebrija.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Apache License v2.0
 * which accompanies this distribution, and is available at
 * http://www.apache.org/licenses/LICENSE-2.0.html
 * 
 * Contributors:
 *    Universidad Antonio de Nebrija - initial API, implementation and documentation
 */
package com.nebrija.riif.model.functions;

import java.util.ArrayList;
import com.nebrija.riif.model.Function;
import com.nebrija.riif.model.Util;
import com.nebrija.riif.model.ValueUnit;

/**
 * Function for agg_gt_n_fail
 * @author asanchep
 *
 */
public class AggGTNFail extends Function{

		//TODO: We ignore the 10^9 value as we suppose that the resulting value is in the same units as the operands
	
		public static final String NAME="agg_gt_n_fail";
		public static final String MTTF="MTTF";
		public static final String FITS="FITS";
		
		/** Check if working with MTTF or FITS*/
		private boolean processingMTTF=true;
		
		/**
		 * Creates an AggGTNFail object
		 */
		public AggGTNFail() {
			// Any number of parameters allowed
			super(AggGTNFail.NAME, -1);
		}

		@SuppressWarnings("unchecked")
		@Override
		protected Object executeFunction(Object... operands) {
			double result = 0d;
			// there must be at least one operand with the number of failures to compare to
			if (operands.length<1){
				return result;
			}
			
			// n in the agg_gt_n_fail 
			double failReq = (Double) operands[0];
			
			// rest of operands
			int numElements = operands.length-1;
			int realElements = numElements;
			
			// The number of real elements is composed by the number of elements, but if any of them is an ArrayList,
			// this element should be replaced by the number of objects in the list
			for(int i=0;i<numElements;i++){
				if(operands[i+1] instanceof ArrayList<?>){
					realElements+=((ArrayList<Object>)operands[i+1]).size()-1;
				}
			}
			
			// Calculate the MTTF value for i elements, i-1, i-2 ... i-n+1 and sum them
			for(int n=0;n<=failReq;n++){
				Object resN = calculateNValue(realElements-n, operands);
				if (resN == null){
					// return a string with the function
					return keepString(operands);
				}
				result+=(Double)resN;
			}
			
			// If operands where in FITS, return the result in FITS
			if(!processingMTTF){
				result=1/result;
			}
			return result;
		}
		
		/**
		 * Returns the original string
		 * @param operands the original operands
		 * @return the string
		 */
		protected Object keepString(Object[] operands){
			StringBuilder notResolved = new StringBuilder();
			for (int i=1; i<operands.length; i++){
				notResolved.append(",").append(operands[i]);
			}
			return NAME+"("+notResolved.toString()+")";
		}
		
		/**
		 * Calculate the MTTF value for N elements 
		 * @param n the number of elements to consider
		 * @param operands all the operands
		 * @return the calculated value or null if it could not be calculated
		 */
		protected Object calculateNValue(int n, Object[] operands){
			double result = 0d;
			
			// number of elements processed.
			int processed = 0;
			for (int i=1; i<operands.length && processed<n; i++){ // TODO: change the operands being ignored. At this moment, the last ones
				if (operands[i] instanceof String){
					return null;
				}else if (operands[i] instanceof ValueUnit){ // ValueUnits define if the values are measured using FITS or MTTF
					ValueUnit vu = (ValueUnit) operands[i];
					// Sum the failure rates
					if(vu.getUnit().equals(FITS)){ // we suppose all the operands in the same units.
						processingMTTF=false;
						result+=vu.getValue();
					}else{
						result+=1/vu.getValue();
					}
					processed++;
				} else if (operands[i] instanceof ArrayList<?>){ // Retrieve each element in the arraylist
					@SuppressWarnings("unchecked")
					ArrayList<Object> al = (ArrayList<Object>)operands[i];
					// Traverse the arraylist
					for(int j=0;j<al.size() && processed<n;j++){
						Object op = al.get(j);
						if(op instanceof String){
							return null;
						}
						// Same as before with the ValueUnit 
						if (op instanceof ValueUnit){
							ValueUnit vu = (ValueUnit) op;
							if(vu.getUnit().equals(FITS)){ // we suppose all the operands in the same units.
								processingMTTF=false;
								result+=vu.getValue();
							}else{
								result+=1/vu.getValue();
							}
						}else { //Let's suppose that unit=FITS if no ValueUnit is provided
							result+=Util.convertToDouble(op);
						}
						processed++;
					}
				}else{ //Let's suppose that unit=FITS if no ValueUnit is provided
					result+=Util.convertToDouble(operands[i]);
					processed++;
				}
				
			}			

			// If working with MTTF, we need to return the result in MTTF
			if(processingMTTF){
				result=1/result;
			}			
			return result;
		}
	}

